/**
 *    Copyright 2006-2017 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.mybatis.generator.plugins;

import org.mybatis.generator.api.GeneratedJavaFile;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.plugins.custome.CustomerConstants;

import java.util.ArrayList;
import java.util.List;

import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;
import static org.mybatis.generator.plugins.custome.CustomerConstants.firstCharToLowCase;

/**
 * 生成controller
 */
public class AdditionalControllerGeneratorPlugin extends PluginAdapter {

    // 项目目录，一般为 src/main/java
    private String targetProject;//默认 dao层的project

    private String controllerPackage;//controller 包路径 default(dao层包路径)//config

    // service接口名后缀
    private String controllerSuffix;//default:(Controller) //

    // service接口的父接口
    private String superControllerInterface;

    private String importTypes;//自定义引入的类

    private static final String import_split = ";";//引入类的分隔符

    private boolean isNeeded = false;//是否需要生成该文件

    private String dtoPackage;//dto 的包路径   //[config]
    private String dtoSuffix;//dto 后缀 (default: DTO)//config

    private String servicePackage;//service 路径  ,default:dao package//config (
    private String serviceSuffix;//service 后缀(default :Service) //config


    @Override
    public boolean validate(List<String> warnings) {
        //handle parameter
        targetProject = properties.getProperty("targetProject");
        importTypes = properties.getProperty("importTypes");

        dtoPackage = properties.getProperty("dtoPackage");

        dtoSuffix = properties.getProperty("dtoSuffix");
        dtoSuffix = stringHasValue(dtoSuffix) ? dtoSuffix : "DTO";

        servicePackage = properties.getProperty("servicePackage");

        serviceSuffix = properties.getProperty("serviceSuffix");
        serviceSuffix = stringHasValue(serviceSuffix) ? serviceSuffix : "Service";

        controllerPackage = properties.getProperty("controllerPackage");

        controllerSuffix = properties.getProperty("controllerSuffix");
        controllerSuffix = stringHasValue(controllerSuffix) ? controllerSuffix : "Controller";

        isNeeded = properties.getProperty("isNeeded").equalsIgnoreCase("true") ? true : false;
        return true;
    }


    @Override
    public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles(IntrospectedTable introspectedTable) {
        if (isNeeded) {
            //define  name
            String modelFullName = introspectedTable.getBaseRecordType();
            String modelShortName = modelFullName.substring(modelFullName.lastIndexOf(".") + 1);
            //check target project
            if (!stringHasValue(targetProject)) {
                //default same as dao file
                targetProject = introspectedTable.getContext().getJavaClientGeneratorConfiguration().getTargetProject();
            }
            //check dto 
            String dtoFullClassName = null;
            if (stringHasValue(dtoPackage)) {
                dtoFullClassName = String.format("%s.%s%s", dtoPackage, modelShortName, dtoSuffix);
            } else {
                dtoFullClassName = modelFullName + dtoSuffix;
            }
            String dtoShortName = modelShortName + dtoSuffix;
            String dtoParameter = firstCharToLowCase(dtoShortName);
            //check service
            if (!stringHasValue(servicePackage)) {
                //if service package not configure, use dao package as default
                servicePackage = introspectedTable.getContext().getJavaClientGeneratorConfiguration().getTargetPackage();
            }
            String serviceFullName = String.format("%s.%s%s", servicePackage, modelShortName, serviceSuffix);//com.a.b.Service
            String serviceShortName = modelShortName + serviceSuffix;//Service
            String serviceParameter = firstCharToLowCase(serviceShortName);//service

            //check controller
            if (!stringHasValue(controllerPackage)) {
                //if controller package not configure, use dao package as default
                controllerPackage = introspectedTable.getContext().getJavaClientGeneratorConfiguration().getTargetPackage();
            }
            String controllerName = String.format("%s.%s%s", controllerPackage, modelShortName, controllerSuffix);

            String controllerMapping = CustomerConstants.generateRequestMapping(modelShortName);//mapping
            String jsonParameterName = "jsonString";// controller method parameter

            FullyQualifiedJavaType controllerType = new FullyQualifiedJavaType(controllerName);
            String controllerParameter = firstCharToLowCase(controllerType.getShortName());
            TopLevelClass topLevelClass = new TopLevelClass(controllerType);
            topLevelClass.setVisibility(JavaVisibility.PUBLIC);
            addImports(topLevelClass);//import class
            topLevelClass.addImportedType(dtoFullClassName);
            topLevelClass.addAnnotation("@Controller");
            topLevelClass.addAnnotation(String.format("@RequestMapping(value = \"/%s\")", CustomerConstants.generateRequestMapping(modelShortName)));
            //add field
            addField(serviceFullName, serviceParameter, controllerType.getShortName(), topLevelClass);
            //***********************add method****************
            //select by  page
            generateSelectPageCode(modelShortName, dtoFullClassName, dtoShortName, dtoParameter, serviceParameter, topLevelClass);
            //select by example
            generateSelectExampleCode(modelShortName, dtoShortName, dtoParameter, serviceParameter, topLevelClass);
            //select by id
            generateSelectIdCode(modelShortName, dtoShortName, serviceParameter, topLevelClass);
            //update by id
            generateUpdateIdCode(modelShortName, dtoShortName, dtoParameter, serviceParameter, jsonParameterName, topLevelClass);
            //delete by id
            generateDeleteIdCode(modelShortName, serviceParameter, jsonParameterName, topLevelClass);
            //add 
            generateAddCode(modelShortName, dtoShortName, dtoParameter, serviceParameter, jsonParameterName, topLevelClass);
            //
            List<GeneratedJavaFile> answer = new ArrayList<GeneratedJavaFile>();
            GeneratedJavaFile controllerJavaFile = new GeneratedJavaFile(topLevelClass, targetProject, context.getJavaFormatter());
            answer.add(controllerJavaFile);
            return answer;
        } else {
            return null;
        }
    }

    /**
     * add one
     *
     * @param modelShortName
     * @param dtoShortName
     * @param dtoParameter
     * @param serviceParameter
     * @param jsonParameterName
     * @param topLevelClass
     */
    private void generateAddCode(String modelShortName, String dtoShortName, String dtoParameter, String serviceParameter, String jsonParameterName, TopLevelClass topLevelClass) {
        Method method = new Method(CustomerConstants.CONTROLLER_ADD);
        addMethodInitAnnotation(method, CustomerConstants.CONTROLLER_ADD_MAPPING);
        method.setReturnType(new FullyQualifiedJavaType("ResponseHelper.RestResponse"));
        method.addParameter(new Parameter(new FullyQualifiedJavaType("HttpServletRequest"), "request"));
        addJsonParameter(method, dtoShortName, dtoParameter);
        method.setVisibility(JavaVisibility.PUBLIC);
        method.addBodyLine("try {");
        method.addBodyLine(String.format("Result<Integer> integerResult = %s.add%s(%s);", serviceParameter, modelShortName, dtoParameter));
        method.addBodyLine("if (integerResult.isSuccess()) {");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._200, integerResult.getData(), \"ok\");");
        method.addBodyLine("} else {");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._500, integerResult.getMsg());");
        method.addBodyLine("}");
        method.addBodyLine("} catch (Exception e) {");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._400, \"参数处理异常\");");
        method.addBodyLine("}");
        topLevelClass.addMethod(method);
    }

    /**
     * delete by id
     *
     * @param modelShortName
     * @param serviceParameter
     * @param jsonParameterName
     * @param topLevelClass
     */
    private void generateDeleteIdCode(String modelShortName, String serviceParameter, String jsonParameterName, TopLevelClass topLevelClass) {
        Method method = new Method(CustomerConstants.CONTROLLER_DELETE_BY_ID);
        method.setVisibility(JavaVisibility.PUBLIC);
        addMethodInitAnnotation(method, CustomerConstants.CONTROLLER_DELETE_BY_ID_MAPPING);
        method.setReturnType(new FullyQualifiedJavaType("ResponseHelper.RestResponse"));
        method.addParameter(new Parameter(new FullyQualifiedJavaType("HttpServletRequest"), "request"));
        addJsonParameter(method, null, null);
        method.addBodyLine("Integer userId = null;");
        method.addBodyLine(String.format("if (StringUtil.isNotEmptyOrNull(%s)) {", jsonParameterName));
        method.addBodyLine("try {");
        method.addBodyLine(String.format("JSONObject jsonObject = JSON.parseObject(%s);", jsonParameterName));
        method.addBodyLine("Object idObj = jsonObject.get(\"id\");");
        method.addBodyLine("int id = Integer.parseInt(idObj.toString());");
        method.addBodyLine(String.format("Result<Boolean> booleanResult = %s.delete%sByIdLogic(id, userId);", serviceParameter, modelShortName));
        method.addBodyLine("if (booleanResult.isSuccess()) {");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._200, \"ok\");");
        method.addBodyLine("} else {");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._500, booleanResult.getMsg());");
        method.addBodyLine("}");
        method.addBodyLine("} catch (Exception e) {");
        method.addBodyLine("e.printStackTrace();");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._500, e.getMessage());");
        method.addBodyLine("}");
        method.addBodyLine("} else {");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._400, \"参数不可为空\");");
        method.addBodyLine("}");
        topLevelClass.addMethod(method);
    }

    /**
     * update by id
     *
     * @param modelShortName
     * @param dtoShortName
     * @param dtoParameter
     * @param serviceParameter
     * @param jsonParameterName
     * @param topLevelClass
     */
    private void generateUpdateIdCode(String modelShortName, String dtoShortName, String dtoParameter, String serviceParameter, String jsonParameterName, TopLevelClass topLevelClass) {
        Method method = new Method(CustomerConstants.CONTROLLER_UPDATE_BY_ID);
        addMethodInitAnnotation(method, CustomerConstants.CONTROLLER_UPDATE_BY_ID_MAPPING);
        method.setReturnType(new FullyQualifiedJavaType("ResponseHelper.RestResponse"));
        method.setVisibility(JavaVisibility.PUBLIC);
        method.addParameter(new Parameter(new FullyQualifiedJavaType("HttpServletRequest"), "request"));
        addJsonParameter(method, dtoShortName, dtoParameter);
        method.addBodyLine(String.format("if (%s.getId() != null) {", dtoParameter));
        method.addBodyLine("try {");
        method.addBodyLine(String.format("Result<Boolean> booleanResult = %s.update%sById(%s);", serviceParameter, modelShortName, dtoParameter));
        method.addBodyLine("if (booleanResult.isSuccess()) {");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._200, \"ok\");");
        method.addBodyLine("} else {");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._500, booleanResult.getMsg());");
        method.addBodyLine("}");
        method.addBodyLine("} catch (Exception e) {");
        method.addBodyLine("e.printStackTrace();");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._500, e.getMessage());");
        method.addBodyLine("}");
        method.addBodyLine("} else {");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._400, \"参数不可为空\");");
        method.addBodyLine("}");
        topLevelClass.addMethod(method);
    }

    /**
     * select by id
     *
     * @param modelShortName
     * @param dtoShortName
     * @param serviceParameter
     * @param topLevelClass
     */
    private void generateSelectIdCode(String modelShortName, String dtoShortName, String serviceParameter, TopLevelClass topLevelClass) {
        Method method = new Method(CustomerConstants.CONTROLLER_SELECT_BY_ID);
        addMethodInitAnnotation(method, CustomerConstants.CONTROLLER_SELECT_BY_ID_MAPPING);
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(new FullyQualifiedJavaType("ResponseHelper.RestResponse"));
        method.addParameter(new Parameter(new FullyQualifiedJavaType("HttpServletRequest"), "request"));
        method.addParameter(new Parameter(FullyQualifiedJavaType.getIntInstance().getPrimitiveTypeWrapper(), "id"));
        method.addBodyLine("if (id == null) {");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._400, \"参数不可为空\");");
        method.addBodyLine("}");
        method.addBodyLine(String.format("Result<%s> result = %s.select%sById(id);", dtoShortName, serviceParameter, modelShortName));
        method.addBodyLine("if (result.isSuccess()) {");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._200, result.getData(), \"ok\");");
        method.addBodyLine("} else {");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._500, result.getMsg());");
        method.addBodyLine("}");
        topLevelClass.addMethod(method);
    }

    /**
     * select by example
     *
     * @param modelShortName
     * @param dtoShortName
     * @param dtoParameter
     * @param serviceParameter
     * @param topLevelClass
     */
    private void generateSelectExampleCode(String modelShortName, String dtoShortName, String dtoParameter, String serviceParameter, TopLevelClass topLevelClass) {
        Method method = new Method(CustomerConstants.CONTROLLER_SELECT_BY_Example);
        method.setVisibility(JavaVisibility.PUBLIC);
        addMethodInitAnnotation(method, CustomerConstants.CONTROLLER_SELECT_BY_Example_MAPPING);
        method.setReturnType(new FullyQualifiedJavaType("ResponseHelper.RestResponse"));
        method.addParameter(new Parameter(new FullyQualifiedJavaType("HttpServletRequest"), "request"));
        method.addParameter(new Parameter(new FullyQualifiedJavaType(dtoShortName), dtoParameter));
        method.addBodyLine(String.format("if (%s == null) {", dtoParameter));
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._400, \"参数不可为空\");");
        method.addBodyLine("}");
        method.addBodyLine(String.format("Result<List<%s>> listResult = %s.select%sByExample(%s);", dtoShortName, serviceParameter, modelShortName, dtoParameter));
        method.addBodyLine("if (listResult.isSuccess()) {");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._200, listResult.getData(), \"ok\");");
        method.addBodyLine("} else {");
        method.addBodyLine("return ResponseHelper.buildResponse(RestResponseCode._500, listResult.getMsg());");
        method.addBodyLine("}");
        topLevelClass.addMethod(method);
    }

    /**
     * select by page
     *
     * @param modelShortName
     * @param dtoFullClassName
     * @param dtoShortName
     * @param dtoParameter
     * @param serviceParameter
     * @param topLevelClass
     */
    private void generateSelectPageCode(String modelShortName, String dtoFullClassName, String dtoShortName, String dtoParameter, String serviceParameter, TopLevelClass topLevelClass) {
        Method method = new Method(CustomerConstants.CONTROLLER_SELECT_BY_PAGE);
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(new FullyQualifiedJavaType("BaseJsonResponse"));
        method.addParameter(new Parameter(new FullyQualifiedJavaType("HttpServletRequest"), "request"));
        method.addParameter(new Parameter(new FullyQualifiedJavaType(dtoFullClassName), dtoParameter));
        addMethodInitAnnotation(method, CustomerConstants.CONTROLLER_SELECT_BY_PAGE_MAPPING);
        method.addParameter(new Parameter(new FullyQualifiedJavaType("int"), "pageSize"));
        method.addParameter(new Parameter(new FullyQualifiedJavaType("int"), "page"));

        method.addBodyLine("BaseJsonResponse baseJsonResponse = new BaseJsonResponse();");
        method.addBodyLine(String.format("if (%s == null) {", dtoParameter));
        method.addBodyLine("baseJsonResponse.setCode(RestResponseCode._400);");
        method.addBodyLine("baseJsonResponse.setMsg(\"参数不可为空\");");
        method.addBodyLine("return baseJsonResponse;");
        method.addBodyLine("}");
        method.addBodyLine(String.format("Result<PageQuery<%s>> pageQueryResult = %s.select%sPage(%s, pageSize, page);", dtoShortName, serviceParameter, modelShortName, dtoParameter));
        method.addBodyLine("//build page");
        method.addBodyLine("baseJsonResponse.handlePageResult(pageQueryResult, baseJsonResponse);");
        method.addBodyLine("return baseJsonResponse;");
        topLevelClass.addMethod(method);
    }

    /**
     * add needed field
     *
     * @param serviceParameter
     * @param serviceFullName
     * @param controllerShortName
     * @param topLevelClass
     */
    private void addField(String serviceFullName, String serviceParameter, String controllerShortName, TopLevelClass topLevelClass) {
        topLevelClass.addImportedType(new FullyQualifiedJavaType(serviceFullName));
        Field loggerField = new Field("logger", new FullyQualifiedJavaType("org.slf4j.Logger"));
        loggerField.setVisibility(JavaVisibility.PRIVATE);
        loggerField.setInitializationString(String.format("LoggerFactory.getLogger(%s.class)", controllerShortName));
        Field serviceField = new Field(serviceParameter, new FullyQualifiedJavaType(serviceFullName));
        serviceField.setVisibility(JavaVisibility.PRIVATE);
        serviceField.addAnnotation("@Resource");
        topLevelClass.addField(loggerField);
        topLevelClass.addField(serviceField);
    }


    /**
     * add controller common imports
     *
     * @param topLevelClass
     */
    private void addImports(TopLevelClass topLevelClass) {
        //common
        topLevelClass.addImportedType(new FullyQualifiedJavaType("com.alibaba.fastjson.JSON"));
        topLevelClass.addImportedType(new FullyQualifiedJavaType("com.alibaba.fastjson.JSONObject"));
//
        topLevelClass.addImportedType(new FullyQualifiedJavaType("org.slf4j.Logger"));
        topLevelClass.addImportedType(new FullyQualifiedJavaType("org.slf4j.LoggerFactory"));
        topLevelClass.addImportedType(new FullyQualifiedJavaType("org.springframework.stereotype.Controller"));
        topLevelClass.addImportedType(new FullyQualifiedJavaType("org.springframework.web.bind.annotation.RequestMapping"));
        topLevelClass.addImportedType(new FullyQualifiedJavaType("org.springframework.web.bind.annotation.ResponseBody"));

        topLevelClass.addImportedType(new FullyQualifiedJavaType("javax.annotation.Resource"));
        topLevelClass.addImportedType(new FullyQualifiedJavaType("javax.servlet.http.HttpServletRequest"));
        topLevelClass.addImportedType(new FullyQualifiedJavaType("java.util.List"));
        //customer
        if (stringHasValue(importTypes)) {
            String[] split = importTypes.split(import_split);
            for (String type : split) {
                topLevelClass.addImportedType(new FullyQualifiedJavaType(type));
            }
        } else {
            //not configure imports ,use default ,need change 
            topLevelClass.addImportedType(new FullyQualifiedJavaType("org.mybatis.generator.plugins.custome.Result"));
            topLevelClass.addImportedType(new FullyQualifiedJavaType("org.mybatis.generator.plugins.custome.BaseJsonResponse"));
            topLevelClass.addImportedType(new FullyQualifiedJavaType("org.mybatis.generator.plugins.custome.StringUtil"));
            topLevelClass.addImportedType(new FullyQualifiedJavaType("org.mybatis.generator.plugins.custome.PageQuery"));
            topLevelClass.addImportedType(new FullyQualifiedJavaType("org.mybatis.generator.plugins.custome.ResponseHelper"));
            topLevelClass.addImportedType(new FullyQualifiedJavaType("org.mybatis.generator.plugins.custome.RestResponseCode"));
        }
    }

    /**
     * 添加 jsonString 参数
     *
     * @param method
     * @param dtoShortName
     * @param dtoParameter @return
     */
    private Parameter addJsonParameter(Method method, String dtoShortName, String dtoParameter) {
        Parameter parameter = null;
        if (stringHasValue(dtoShortName) && stringHasValue(dtoParameter)) {
            parameter = new Parameter(new FullyQualifiedJavaType(dtoShortName), dtoParameter);
        } else {
            parameter = new Parameter(FullyQualifiedJavaType.getStringInstance(), "jsonString");
        }
        parameter.addAnnotation("@RequestBody");
        method.addParameter(parameter);
        return parameter;
    }

    /**
     * method 添加初始化注解
     *
     * @param method
     * @param mapping
     */
    private void addMethodInitAnnotation(Method method, String mapping) {
        method.addAnnotation("@ResponseBody");
        method.addAnnotation(String.format("@RequestMapping(value = \"%s\")", mapping));
    }
}